"""
sdn.py defines services to start Open vSwitch and the Ryu SDN Controller.
"""

import re
import socket
import binascii
import netaddr
from typing import Dict, List, Tuple

from core.nodes.base import CoreNode
from core.services.coreservices import CoreService


class SdnService(CoreService):
    """
    Parent class for SDN services.
    """

    group: str = "SDN"

    @classmethod
    def generate_config(cls, node: CoreNode, filename: str) -> str:
        return ""


class OvsService(SdnService):
    name: str = "OvsService"
    group: str = "SDN"
    executables: Tuple[str, ...] = ("ovs-ofctl", "ovs-vsctl")
    dirs: Tuple[str, ...] = (
        "/etc/openvswitch",
        "/var/run/openvswitch",
        "/var/log/openvswitch",
    )
    configs: Tuple[str, ...] = ("OvsService.sh", "KillOvs.sh")
    startup: Tuple[str, ...] = ("bash OvsService.sh",)
    shutdown: Tuple[str, ...] = ("bash KillOvs.sh",)
    # shutdown: Tuple[str, ...] = ("killall ovs-vswitchd", "killall ovsdb-server")

    @classmethod
    def generate_config(cls, node: CoreNode, filename: str) -> str:
        if(filename.__eq__("OvsService.sh")):
            return cls.generate_ovs_startup(node, filename)
        elif(filename == "KillOvs.sh"):
            return cls.generate_ovs_stop(node, filename)


    @classmethod
    def generate_ovs_startup(cls, node: CoreNode, filename: str) -> str:
 
        ovsbr_name = "ovsbr%s"%(node.id)
        cfg = "#!/bin/sh\n"
        cfg += "## auto-generated by OvsService (core/services/sdn.py/OvsService)\n"
    
        cfg += "## copy db file\n"
        cfg += "cp /usr/local/etc/openvswitch/conf.db /usr/local/etc/openvswitch/conf-%d.db\n" % node.id
    
        cfg += "## start the ovsdb-server\n"
        cfg += "ovsdb-server --remote=punix:db-%d.sock --remote=db:Open_vSwitch,Open_vSwitch,manager_options --private-key=db:Open_vSwitch,SSL,private_key --certificate=db:Open_vSwitch,SSL,certificate --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert --pidfile=ovsdb-server-%d.pid --detach --log-file=ovsdb-server-%d.log /usr/local/etc/openvswitch/conf-%d.db\n" % (node.id, node.id, node.id, node.id)
        cfg += "## Initiate the db\n"
        cfg += "ovs-vsctl --db=unix:db-%d.sock --no-wait init\n" % node.id
        cfg += "## start the ovs\n"
        cfg += "ovs-vswitchd --pidfile=ovs-vswitchd-%d.pid --detach --log-file=ovs-vswitchd-%d.log unix:db-%d.sock\n" % (node.id, node.id, node.id)
    
        cfg += "## add bridge\n"
        cfg += "ovs-vsctl --db=unix:db-%d.sock add-br %s -- set Bridge %s fail-mode=secure\n" % (node.id, ovsbr_name, ovsbr_name)  # echo 1 | sudo
        cfg += "\n## Now add all our interfaces as ports to the switch\n"
    
        ifaces = node.get_ifaces()
    
        port_num = 1
        for iface in ifaces:
            is_ctr_link = False
            ctr_ip = None
            # wired link to controller
            if node.session.ctr_link.get(node.name):
                if iface.name == node.session.ctr_link[node.name][0]:
                    is_ctr_link = True
                    ctr_ip = node.session.ctr_link[node.name][1]
            # wireless link to controller
            if node.session.ctr_link.get(iface.net.name):
                is_ctr_link = True
                ctr_ip = node.session.ctr_link[iface.net.name]
    
            if is_ctr_link:
                cfg += "## add controller\n"
                cfg += "ovs-vsctl --db=unix:db-%d.sock set-controller %s tcp:%s:6633\n\n" % (node.id, ovsbr_name, ctr_ip)
            else:
                if 'ctrl' in iface.name:  # ctrl interface should be excluded
                    pass
                else:
                    cfg += "## ---------ovs_br: %s, iface: %s-----------------\n" % (ovsbr_name, iface.name)
                    cfg += "## iface.net_id: %s\n" % iface.net_id
                    cfg += "## add %s to ovs br\n" % iface.name
                    cfg += "ovs-vsctl --db=unix:db-%d.sock add-port %s %s -- set Interface %s ofport_request=%d\n" % (node.id, ovsbr_name, iface.name, iface.name, port_num)
                    cfg += "ovs-ofctl add-flow %s table=0,in_port=%d,arp,arp_tpa=%s,arp_op=1,actions=move:\"NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[]\",mod_dl_src:\"%s\",load:\"0x02->NXM_OF_ARP_OP[]\",move:\"NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[]\",load:\"0x%s->NXM_NX_ARP_SHA[]\",move:\"NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[]\",load:\"0x%s->NXM_OF_ARP_SPA[]\",in_port \n" % (ovsbr_name, port_num, str(iface.ip4s[0].ip), iface.mac, (str(iface.mac).replace(":", "")), (binascii.hexlify((socket.inet_aton(str(iface.ip4s[0].ip))))).decode('ascii'))#mac no problem
                    cfg += "ovs-ofctl add-flow %s table=0,in_port=%d,icmp,nw_dst=%s,icmp_type=8,icmp_code=0,actions=push:\"NXM_OF_ETH_SRC[]\",push:\"NXM_OF_ETH_DST[]\",pop:\"NXM_OF_ETH_SRC[]\",pop:\"NXM_OF_ETH_DST[]\",push:\"NXM_OF_IP_SRC[]\",push:\"NXM_OF_IP_DST[]\",pop:\"NXM_OF_IP_SRC[]\",pop:\"NXM_OF_IP_DST[]\",load:\"0xff->NXM_NX_IP_TTL[]\",load:\"0->NXM_OF_ICMP_TYPE[]\",in_port \n" % (ovsbr_name, port_num, str(iface.ip4s[0].ip))
            port_num += 1
        return cfg

    @classmethod
    def generate_ovs_stop(cls, node: CoreNode, filename: str) -> str:
        cfg = "#!/bin/sh\n"
        cfg += "kill `cd /usr/local/var/run/openvswitch && cat ovsdb-server-%d.pid ovs-vswitchd-%d.pid`\n"%(node.id, node.id)
        cfg += "cd /usr/local/etc/openvswitch && rm conf-%d.db\n" % node.id
        cfg += "cd /usr/local/etc/openvswitch && rm .conf-%d.db.~lock~\n" % node.id
        return cfg
